
if (not commonlib.USE_LCURL or not commonlib.USE_LUV) then 
    -- commonlib.exit("please install luv lua-cURL library!!!")
    return {} 
end

commonlib.__lcurl__ = require("cURL.cURL");
local __lcurl__ = commonlib.__lcurl__;
local __luv__ = commonlib.__luv__;

local CURL_POLL_IN = __lcurl__.POLL_IN;              -- 1
local CURL_POLL_OUT = __lcurl__.POLL_OUT;            -- 2
local CURL_POLL_INOUT = __lcurl__.POLL_INOUT;        -- 3 
local CURL_POLL_REMOVE = __lcurl__.POLL_REMOVE;      -- 4
local __multi_handle__ = nil;

local function bind(self, fn)
    return function(...) return fn(self, ...) end
end

local function string_trim(str, ch)
	ch = ch or "%s";
    str = string.gsub(str, "^" .. ch .. "*", "");
    str = string.gsub(str, ch .. "*$", "");
    return str;
end

local function curl_check_multi_info()
    while(true) do
        local easy, ok, err = __multi_handle__:info_read(true)
        if (easy == 0 or not easy) then break end
        if (ok) then
            easy.data:OnFinish();
        else
            print("CURL curl_check_multi_info ERROR:", err);
            easy.data:Close();
        end
    end
end
    
local Curl = {

};

function Curl:new(obj)
    obj = obj or {};
    setmetatable(obj or {}, {__index = Curl});
    obj:ctor();
    return obj;
end

function Curl:ctor()
    self.__easy_handle__ = __lcurl__.easy();
    self.__easy_handle__.data = self;
    self.__easy_handle__:setopt_headerfunction(self.OnHeader, self);
    self.__easy_handle__:setopt_writefunction(self.OnWrite, self);
    self.__easy_handle__:setopt_readfunction(self.OnRead, self);
    self.__easy_opt__ = {
        fresh_connect = true,
        forbid_reuse  = true, 
        timeout_ms = 3000,
    };
    self.__request_header__ = {};
    self.__request_body__ = nil;
    self.__response_header__ = {};
    self.__response_body__ = nil;
    self.__status_code__ = 0;
end

function Curl:SetRequestHeader(key, val)
    self.__request_header__[key] = val;
    return self;
end

function Curl:Init(opt)
    opt = opt or {};
    -- 保存上传文本
    self.__request_body__ = opt.request_body or "";
    opt.text = nil;
    -- 请求头
    for key, val in pairs(opt.header or {}) do
        self.__request_header__[key] = val;
    end
    opt.header = nil;
    -- onfinish
    self.__on_finish__ = opt.on_finish;
    opt.on_finish = nil;

    -- opt
    for key, val in pairs(opt) do
        self.__easy_opt__[key] = val;
    end
    return self;
end

function Curl:Close()
    self:StopPoll();
    __multi_handle__:remove_handle(self.__easy_handle__);
    self.__easy_handle__:close();
    self.__easy_handle__ = nil;
    if (self.__mime__) then 
        self.__mime__:free();
        self.__mime__ = nil;
    end
end

function Curl:OnHeader(header)
    table.insert(self.__response_header__, header);
end

function Curl:OnWrite(body)
    self.__response_body__ = body;
end

function Curl:OnRead(max_read_cournt)
    local text = self.__request_body__;
    self.__request_body__ = nil;
    return text;
end

function Curl:GetStatusCode()
    return self.__status_code__;
end

function Curl:GetBody()
    return self.__response_body__;
end

function Curl:GetData()
    return self.__response_data__;
end

function Curl:OnFinish()
    self:Close();
    self.__status_code__ = tonumber(string.match(self.__response_header__[1], " (%d+) "));
    for _, header in ipairs(self.__response_header__) do
		local split_pos = string.find(header, ":", 1, true);
        if (split_pos) then
            local key = string.sub(header, 1, split_pos -1);
            local value = string.sub(header, split_pos + 1);
            self.__response_header__[string.lower(string_trim(key, "%s"))] = string_trim(value, "%s");
        end
    end

    -- if (self.__response_header__["content-type"] == "application/json") then
    --     self.__response_data__ = commonlib.JsonDecode(self.__request_body__);
    -- end

    if (type(self.__on_finish__) == "function") then
        self.__on_finish__(self.__status_code__, self.__response_body__, self.__response_header__);
    end
end

function Curl:OnPoll(err, events)
    if (err) then
        print("CURL OnPoll ERROR:", err);
        self:Close();
        return;
    end

    local mask = 0;
    if (events == "r") then mask = __lcurl__.CSELECT_IN end
    if (events == "w") then mask = __lcurl__.CSELECT_OUT end
    if (events == "rw") then mask = __lcurl__.CSELECT_IN + __lcurl__.CSELECT_OUT end
    __multi_handle__:socket_action(self.__fd__, mask);
    curl_check_multi_info()
end

function Curl:StartPoll(fd, events)
    if (self.__fd__ ~= fd) then
        self:StopPoll();
        self.__fd__ = fd;
        self.__poll__ = __luv__.new_socket_poll(fd);
    end
    __luv__.poll_start(self.__poll__, events, bind(self, self.OnPoll));
end

function Curl:StopPoll()
    if (not self.__poll__) then return end
    __luv__.poll_stop(self.__poll__);
    self.__poll__:close();
    self.__poll__ = nil;
    self.__fd__ = nil;
end

function Curl:AddPart(part)
    if (not self.__mime__) then
        self.__mime__ = self.__easy_handle__:mime();
        self.__alt__ = self.__easy_handle__:mime();
        self.__mime__:addpart():subparts(self.__alt__, "multipart/alternative", {"Content-Disposition: inline"});
        self.__easy_opt__.mimepost = self.__mime__;
    end
    self.__alt__:addpart(part);
    return self;
end

function Curl:Perform()
    -- httpheader
    self.__easy_opt__.httpheader = self.__easy_opt__.httpheader or {};
    for key, val in pairs(self.__request_header__) do
        table.insert(self.__easy_opt__.httpheader, key .. ":" .. val);
    end
    -- setopt
    self.__easy_handle__:setopt(self.__easy_opt__);

    commonlib.echo(self.__easy_opt__);

    -- add easy
    __multi_handle__:add_handle(self.__easy_handle__);

    return self;
end

local function on_handle_socket(easy, fd, action)
    local curl = easy.data
    if (action == CURL_POLL_IN or action == CURL_POLL_OUT or action == CURL_POLL_INOUT) then
        local flag = "rw";
        if (action == CURL_POLL_IN) then flag = "r" end
        if (action == CURL_POLL_OUT) then flag = "w" end
        if (action == CURL_POLL_INOUT) then flag = "rw" end
        -- print(fd, action, flag)
        curl:StartPoll(fd, flag);
    elseif (action == __lcurl__.POLL_REMOVE) then
        curl:StopPoll();
    else
        print("CURL on_handle_socket INFO", fd, action)
    end
end

local function on_handle_timeout(ms)
    if ms <= 0 then ms = 1 end
    local timer = __luv__.new_timer();
    timer:start(ms, 0, function() 
        timer:stop();
        timer:close();
        __multi_handle__:socket_action(__lcurl__.SOCKET_TIMEOUT);
        curl_check_multi_info()
    end);
end

__multi_handle__ = __lcurl__.multi({
    socketfunction = on_handle_socket,
    timerfunction  = on_handle_timeout,
});

commonlib.__multi_handle__ = __multi_handle__;

function commonlib.HttpGet(url)
    return Curl:new():Init({
        url = url,
        httpget = true,
        customrequest = "GET",
    }):Perform();
end

function commonlib.HttpPost(url)
    return Curl:new():Init({
        url = url, 
        post = true,
        customrequest = "POST",
        postfields = commonlib.JsonEncode(data),
    }):SetRequestHeader("Content-Type", "application/json"):Perform();
end

function commonlib.HttpDelete(url, data)
    return Curl:new():Init({
        url = url,
        customrequest = "DELETE",
    }):Perform();
end

function commonlib.HttpPut(url, data)
    return Curl:new():Init({
        url = url, 
        put = true,
        customrequest = "PUT",
        postfields = commonlib.JsonEncode(data),
    }):SetRequestHeader("Content-Type", "application/json"):Perform();
end

function commonlib.SendEMail(opt)
    local httpheader = {};
    local to_address = opt.to or "765485868@qq.com";
    local from_address = opt.from or "noreply@wxaxiaoyao.cn";
    table.insert(httpheader, "Date: " .. os.date("%a %d %b %Y %H:%M:%S +0800"));
    table.insert(httpheader, "From: " .. string.format("%s <%s>", opt.from_alias or "", from_address));
    table.insert(httpheader, "To: " .. to_address);
    table.insert(httpheader, "Subject: " .. (opt.subject or "LuiGui"));
    return Curl:new():Init({
        url = opt.url or "smtp.mxhichina.com:587",
        mail_from = from_address,
        mail_rcpt = {to_address},
        httpheader = httpheader,
        upload = true,
        verbose = true,
        ssl_verifyhost = false;
        ssl_verifypeer = false;
        username = opt.username or "noreply@wxaxiaoyao.cn",
        password = opt.password or "123456",
        -- debugfunction = function(type, data)
        --     print("==", type, data);
        -- end
    }):AddPart({
        data = opt.html,
        name = "email.html",
        type = "text/html",
    }):Perform();
end

function commonlib.SendMail(opt)
    local text = "Date: " .. os.date("%a %d %b %Y %H:%M:%S +0800");  
    local to_address = opt.to or "765485868@qq.com";
    local from_address = opt.from or "noreply@wxaxiaoyao.cn";
    text = text .. "\nFrom: " .. string.format("%s <%s>", opt.from_alias or "", from_address);
    text = text .. "\nTo: " .. to_address; 
    text = text .. "\nSubject: " .. (opt.subject or "LuiGui");
    text = text .. "\n\n";
    text = text .. (opt.text or "") .. "\n";
    return Curl:new():Init({
        request_body = text,
        url = opt.url or "smtp.mxhichina.com:587",
        mail_from = from_address,
        mail_rcpt = {to_address},
        upload = true,
        -- verbose = true,
        username = opt.username or "noreply@wxaxiaoyao.cn",
        password = opt.password or "123456",
        ssl_verifyhost = false;
        ssl_verifypeer = false;
    }):Perform();
end

function commonlib.DownLoad(opt)
    local filepath = opt.filepath;
    if (not filepath) then return end
    local old_on_finish = opt.on_finish;
    local function call_old_on_finish(...)
        if (type(old_on_finish) == "function") then
            old_on_finish(...);
        end
    end

    opt.on_finish = function(status_code, request_body)
        if (status_code ~= 200) then return call_old_on_finish(status_code, request_body) end 
        local file, err = io.open(filepath, "wb");
        if (not file) then
            print("CURL Download ERROR:", err);
            return call_old_on_finish(status_code, request_body);
        end
        file:write(request_body);
        file:close();
        return call_old_on_finish(status_code, request_body);
    end
    return commonlib.HttpGet(opt);
end

function commonlib.Upload(opt)
end

return Curl;